package com.zjdiepu.www.commpent;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.annotation.PreDestroy;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.SSLInitializationException;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import com.zjdiepu.www.base.lang.HttpClientException;

/**
 * http请求客户端
 */
@Component
@PropertySource(value = { "classpath:httpclient.properties" })
public class HttpClient implements InitializingBean {
    private String certPathname;
    private String certPassword;
    private CloseableHttpClient httpClient = null;
    private IdleConnectionMonitorThread idleConnectionMonitorThread = null;
    @Value("${httpclient.connect_max_total}")
    private int connectMaxTotal = 200;// 连接池最大连接数
    @Value("${httpclient.connect_max_default}")
    private int connectMaxDefault = 20;// 连接池每个连接最大数
    @Value("${httpclient.connect_recycling_time}")
    private int connectRecyclingTime = 5;// 连接回收检测时间(秒)
    @Value("${httpclient.connect_free_timeout}")
    private int connectFreeTimeout = 30;// 连接空闲回收时间(秒)
    @Value("${httpclient.connect_keep_alive_duration}")
    private int connectKeepAliveDuration = 60;// 连接维持时间(秒)
    @Value("${httpclient.connect_retry_number}")
    private int connectRetryNumber = 5;
    private String proxyUri;//代理ip
    private int proxyPort;//代理端口
    private String proxyScheme;//代理协议
    private String proxyUserName;//代理服务器用户名
    private String proxyPassword;//代理服务器密码
    private Charset charset = Consts.UTF_8;
    private final static int IO_ERROR_HTTP_STATUSCODE = 408;//io异常http状态码
    private final static int AUTHENTICATION_OR_CREATE_HTTPS_EXCEPTION = 500;//http client 创建异常
    private static final Logger logger = LoggerFactory.getLogger(HttpClient.class);

    //每个请求的超时时间:简单数据
    @Value("${httpclient.connect_timeout}")
    private int connectTimeout = 1500;//默认连接超时时间 //请求超时,连接超时时间(单位毫秒) 
    @Value("${httpclient.connect_request_timeout}")
    private int connectRequestTimeout = 1500;//默认请求超时时间
    @Value("${httpclient.socket_timeout}")
    private int socketTimeout = 3000;//默认socket超时时间 //响应超时,读数据超时时间(单位毫秒)
    
    //每个请求的超时时间:文件传输
    private int fileTransferConnectTimeout = 3000;//默认连接超时时间 //请求超时,连接超时时间(单位毫秒) 
    private int fileTransferConnectRequestTimeout = 3000;//默认请求超时时间
    private int fileTransferSocketTimeout = 30000;//默认socket超时时间 //响应超时,读数据超时时间(单位毫秒)

    public HttpClient() {
    }

    public HttpClient(int connectMaxTotal, int connectMaxDefault, int connectRecyclingTime, int connectFreeTimeout,
                      int connectKeepAliveDuration, int connectRetryNumber, String charset) {
        this.connectMaxTotal = connectMaxTotal;
        this.connectMaxDefault = connectMaxDefault;
        this.connectRecyclingTime = connectRecyclingTime;
        this.connectFreeTimeout = connectFreeTimeout;
        this.connectKeepAliveDuration = connectKeepAliveDuration;
        this.connectRetryNumber = connectRetryNumber;
        this.charset = Charset.forName(charset);
    }

    public HttpClient(String certPathname, String certPassword, int connectMaxTotal,
    		int connectMaxDefault, int connectRecyclingTime, int connectFreeTimeout, int connectKeepAliveDuration,
    		int connectRetryNumber, String charset) {
    	this.certPathname = certPathname;
    	this.certPassword = certPassword;
    	
    	this.connectMaxTotal = connectMaxTotal;
    	this.connectMaxDefault = connectMaxDefault;
    	this.connectRecyclingTime = connectRecyclingTime;
    	this.connectFreeTimeout = connectFreeTimeout;
    	this.connectKeepAliveDuration = connectKeepAliveDuration;
    	this.connectRetryNumber = connectRetryNumber;
    	
    	this.charset = Charset.forName(charset);
    }
    
    public HttpClient(String certPathname, String certPassword, int connectMaxTotal,
            int connectMaxDefault, int connectRecyclingTime, int connectFreeTimeout, int connectKeepAliveDuration,
            int connectRetryNumber, String charset,int connectTimeout, int connectRequestTimeout, int socketTimeout) {
			this.certPathname = certPathname;
			this.certPassword = certPassword;
			
			this.connectMaxTotal = connectMaxTotal;
			this.connectMaxDefault = connectMaxDefault;
			this.connectRecyclingTime = connectRecyclingTime;
			this.connectFreeTimeout = connectFreeTimeout;
			this.connectKeepAliveDuration = connectKeepAliveDuration;
			this.connectRetryNumber = connectRetryNumber;
			
			this.charset = Charset.forName(charset);
			
			this.connectTimeout = connectTimeout;
			this.connectRequestTimeout = connectRequestTimeout;
			this.socketTimeout = socketTimeout;
	}
    
    public HttpClient(int connectMaxTotal,
    		int connectMaxDefault, int connectRecyclingTime, int connectFreeTimeout, int connectKeepAliveDuration,
    		int connectRetryNumber, String charset,int fileTransferConnectTimeout, int fileTransferConnectRequestTimeout, int fileTransferSocketTimeout) {
    	this.connectMaxTotal = connectMaxTotal;
    	this.connectMaxDefault = connectMaxDefault;
    	this.connectRecyclingTime = connectRecyclingTime;
    	this.connectFreeTimeout = connectFreeTimeout;
    	this.connectKeepAliveDuration = connectKeepAliveDuration;
    	this.connectRetryNumber = connectRetryNumber;
    	
    	this.charset = Charset.forName(charset);
    	
    	this.fileTransferConnectTimeout = fileTransferConnectTimeout;
    	this.fileTransferConnectRequestTimeout = fileTransferConnectRequestTimeout;
    	this.fileTransferSocketTimeout = fileTransferSocketTimeout;
    }
    
	public HttpClient(String certPathname, String certPassword, int connectMaxTotal,
			int connectMaxDefault, int connectRecyclingTime, int connectFreeTimeout, int connectKeepAliveDuration,
			int connectRetryNumber, Charset charset, int connectTimeout, int connectRequestTimeout, int socketTimeout,
			int fileTransferConnectTimeout, int fileTransferConnectRequestTimeout, int fileTransferSocketTimeout) {
		super();
		this.certPathname = certPathname;
		this.certPassword = certPassword;
		
		this.connectMaxTotal = connectMaxTotal;
		this.connectMaxDefault = connectMaxDefault;
		this.connectRecyclingTime = connectRecyclingTime;
		this.connectFreeTimeout = connectFreeTimeout;
		this.connectKeepAliveDuration = connectKeepAliveDuration;
		this.connectRetryNumber = connectRetryNumber;
		
		this.charset = charset;
		
		this.connectTimeout = connectTimeout;
		this.connectRequestTimeout = connectRequestTimeout;
		this.socketTimeout = socketTimeout;
		
		this.fileTransferConnectTimeout = fileTransferConnectTimeout;
		this.fileTransferConnectRequestTimeout = fileTransferConnectRequestTimeout;
		this.fileTransferSocketTimeout = fileTransferSocketTimeout;
	}

	@Override
    public void afterPropertiesSet() throws Exception {
        PoolingHttpClientConnectionManager cm = getPoolingHttpClientConnectionManager();
        ConnectionKeepAliveStrategy myStrategy = getConnectionKeepAliveStrategy();
        HttpRequestRetryHandler myRetryHandler = getHttpRequestRetryHandler();
        HttpClientBuilder httpClientBuilder = HttpClients.custom().setConnectionManager(cm)// 设置连接池
                .setKeepAliveStrategy(myStrategy)// 设置连接存活策略
                .setRetryHandler(myRetryHandler);// 设置请求重试;

        if (StringUtils.isNotEmpty(proxyUri) && proxyPort != 0) {
            HttpHost httpHost = new HttpHost(proxyUri, proxyPort, proxyScheme);
            httpClientBuilder.setProxy(httpHost);
            if (StringUtils.isNotEmpty(proxyUserName) && StringUtils.isNotEmpty(proxyPassword)) {
                CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
                credentialsProvider.setCredentials(new AuthScope(httpHost),
                        new UsernamePasswordCredentials(proxyUserName, proxyPassword));
                httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
            }
        }
        httpClient = httpClientBuilder.build();
        idleConnectionMonitorThread = new IdleConnectionMonitorThread(cm, connectRecyclingTime, connectFreeTimeout);// 设置连接回收策略
        idleConnectionMonitorThread.start();
    }

    /**
     * httpGet请求
     *
     * @param paramMap  请求参数
     * @param headerMap http请求头
     * @return http返回值
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    public String get(Map<String, String> paramMap, Map<String, String> headerMap) throws HttpClientException, SocketTimeoutException {
        return get(paramMap, headerMap);
    }

    /**
     * httpGet请求
     *
     * @param paramMap 请求参数
     * @return http返回值
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    public String get(Map<String, String> paramMap) throws HttpClientException, SocketTimeoutException {
        return get(paramMap, null);
    }

    /**
     * httpGet请求
     *
     * @param uri      uri地址
     * @param paramMap 请求参数
     * @return http返回值
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    public String get(String uri, Map<String, String> paramMap) throws HttpClientException, SocketTimeoutException {
        return get(uri, paramMap, null);
    }

    /**
     * httpGet请求
     *
     * @param uri       uri地址
     * @param paramMap  请求参数
     * @param headerMap http请求头
     * @return http返回值
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    public String get(String uri, Map<String, String> paramMap, Map<String, String> headerMap) throws HttpClientException, SocketTimeoutException {
        if (paramMap != null) {
            uri = uri + "?" + StringUtils.join(paramMap.entrySet().stream().
                    map(p -> p.getKey() + "=" + p.getValue()).collect(Collectors.toList()), '&');
        }
        try {
            return getCall(uri, headerMap);
        } catch (SocketTimeoutException e) {// 服务器响应超时：判断情况后提交/取消
        	throw e;
		} catch (IOException e) {
            throw new HttpClientException(IO_ERROR_HTTP_STATUSCODE, e);
        }
    }


    /**
     * postFrom请求
     *
     * @param paramMap 请求参数
     * @return http返回值
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    public String postForm(Map<String, String> paramMap) throws HttpClientException, SocketTimeoutException {
        return postForm(paramMap, null);
    }

    /**
     * postFrom请求
     *
     * @param paramMap  请求参数
     * @param headerMap http请求头
     * @return http返回值
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    public String postForm(Map<String, String> paramMap, Map<String, String> headerMap) throws HttpClientException, SocketTimeoutException {
        return postForm(paramMap, headerMap);
    }

    /**
     * postFrom请求
     *
     * @param uri      uri地址
     * @param paramMap 请求参数
     * @return http返回值
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    public String postForm(String uri, Map<String, String> paramMap) throws HttpClientException, SocketTimeoutException {
        return postForm(uri, paramMap, null);
    }

    /**
     * postFrom请求
     *
     * @param uri       uri地址
     * @param paramMap  请求参数
     * @param headerMap http请求头
     * @return http返回值
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    public String postForm(String uri, Map<String, String> paramMap, Map<String, String> headerMap) throws HttpClientException, SocketTimeoutException {
        try {
            return postFormCall(uri, paramMap, headerMap);
        } catch (SocketTimeoutException e) {// 服务器响应超时：判断情况后提交/取消
        	throw e;
		} catch (IOException e) {
            throw new HttpClientException(IO_ERROR_HTTP_STATUSCODE, e);
        }
    }

    /**
     * postJson请求
     *
     * @param requestJson 请求参数
     * @param headerMap   http请求头
     * @return http返回值
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    public String postJson(String requestJson, Map<String, String> headerMap) throws HttpClientException, SocketTimeoutException {
        return postJson(requestJson, headerMap);
    }


    /**
     * postJson请求
     *
     * @param uri         uri地址
     * @param requestJson 请求参数
     * @return http返回值
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    public String postJson(String uri, String requestJson) throws HttpClientException, SocketTimeoutException {
        return postJson(uri, requestJson, null);
    }


    /**
     * postJson请求
     *
     * @param uri         uri地址
     * @param requestJson 请求参数
     * @param headerMap   http请求头
     * @return http返回值
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    public String postJson(String uri, String requestJson, Map<String, String> headerMap) throws HttpClientException, SocketTimeoutException {
        return postJsonCall(uri, false, requestJson, headerMap);
    }

    /**
     * 请求的postJson请求
     *
     * @param requestJson 请求参数
     * @param headerMap   http请求头
     * @return http返回值
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    public String postJsonUseCert(String requestJson, Map<String, String> headerMap) throws HttpClientException, SocketTimeoutException {
        return postJsonUseCert(requestJson, headerMap);
    }

    /**
     * 请书的postJson请求
     *
     * @param uri         uri地址
     * @param requestJson 请求参数
     * @return http返回值
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    public String postJsonUseCert(String uri, String requestJson) throws HttpClientException, SocketTimeoutException {
        return postJsonUseCert(uri, requestJson, null);
    }

    /**
     * 请书的postJson请求
     *
     * @param uri         uri地址
     * @param requestJson 请求参数
     * @param headerMap   http请求头
     * @return http返回值
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    public String postJsonUseCert(String uri, String requestJson, Map<String, String> headerMap) throws HttpClientException, SocketTimeoutException {
        return postJsonCall(uri, true, requestJson, headerMap);
    }

    /**
     * 关闭方法
     *
     * @throws IOException
     */
    public void close() throws IOException {
        synchronized (this) {
            if (httpClient != null)
                httpClient.close();
            if (idleConnectionMonitorThread != null)
                idleConnectionMonitorThread.shutdown();
        }
    }

    /**
     * 发送post表单请求
     *
     * @param uri                   uri地址
     * @param map                   请求参数
     * @param headerMap             http请求头
     * @return http返回值
     * @throws ClientProtocolException
     * @throws IOException
     * @throws HttpClientException
     */
    private String postFormCall(String uri, Map<String, String> map, Map<String, String> headerMap) throws ClientProtocolException, IOException, HttpClientException {
        HttpPost httpPost = new HttpPost(getStandardUri(uri));
        List<NameValuePair> formParams = map.entrySet().stream().
                map(p -> new BasicNameValuePair(p.getKey(), p.getValue())).collect(Collectors.toList());
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, charset);
        httpPost.setEntity(entity);
        return httpCall(httpPost, httpClient, headerMap);
    }
    
    /**
     * @param uri     http请求地址
     * @param isUseCert   是否使用证书
     * @param requestJson 请求内容
     * @return 返回值
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    private String postJsonCall(String uri, boolean isUseCert, String requestJson, Map<String, String> headerMap) throws HttpClientException, SocketTimeoutException {
        try {
            return postCall(uri, requestJson, "application/json", isUseCert, headerMap);
        } catch (SocketTimeoutException e) {// 服务器响应超时：判断情况后提交/取消
        	throw e;
		} catch (IOException e) {
        	logger.error("http request happened exception.{}",e);
            throw new HttpClientException(IO_ERROR_HTTP_STATUSCODE, e);
        } catch (UnrecoverableKeyException | SSLInitializationException | KeyManagementException | KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
        	logger.error("created http client happened exception.{}",e);
        	throw new HttpClientException(AUTHENTICATION_OR_CREATE_HTTPS_EXCEPTION, e);
		}
    }

    /**
     * 以json格式发送post请求
     *
     * @param uri                   uri地址
     * @param requestContent        请求参数
     * @param ctString              Content-Type
     * @param isUseCert             是否使用证书
     * @param headerMap             http请求头
     * @return http返回值
     * @throws KeyStoreException
     * @throws FileNotFoundException
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws CertificateException
     * @throws UnrecoverableKeyException
     * @throws SSLInitializationException
     * @throws KeyManagementException
     * @throws HttpClientException
     */
    private String postCall(String uri, String requestContent, String ctString, boolean isUseCert, Map<String, String> headerMap)
            throws KeyStoreException, FileNotFoundException, IOException, NoSuchAlgorithmException,
            CertificateException, UnrecoverableKeyException, SSLInitializationException, KeyManagementException, HttpClientException {
        CloseableHttpClient httpclient;
        if (isUseCert) {
            httpclient = getSSLHttpClient();
        } else {
            httpclient = httpClient;
        }

        HttpPost httpPost = new HttpPost(getStandardUri(uri));
        StringEntity stringEntity = new StringEntity(requestContent, charset);
        stringEntity.setContentType(ctString);
        httpPost.setEntity(stringEntity);
        try {
            return httpCall(httpPost, httpclient, headerMap);
        } finally {
            if (isUseCert)
                httpclient.close();
        }
    }

    /**
     * 发送get请求
     *
     * @param uri                   uri地址
     * @param headerMap             http请求头
     * @return http返回值
     * @throws ClientProtocolException
     * @throws IOException
     * @throws HttpClientException
     */
    private String getCall(String uri, Map<String, String> headerMap) throws ClientProtocolException, IOException, HttpClientException {
        HttpGet httpGet = new HttpGet(getStandardUri(uri));
        return httpCall(httpGet, httpClient, headerMap);
    }

    /**
     * @param request    httpRequest
     * @param httpClient
     * @param headerMap  http请求头
     * @return http返回值
     * @throws IOException
     * @throws HttpClientException
     */
    private String httpCall(HttpRequestBase request, CloseableHttpClient httpClient, Map<String, String> headerMap) throws IOException, HttpClientException {
    	RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(connectTimeout)
                .setConnectionRequestTimeout(connectRequestTimeout)
                .setSocketTimeout(socketTimeout).build();
    	 request.setConfig(requestConfig);
    	
    	Header[] headers = getHeaders(headerMap);
        if (headers.length != 0)
            request.setHeaders(headers);
        try (CloseableHttpResponse response = httpClient.execute(request)) {
        	Header[] responseHeaders = response.getHeaders("X-Ca-Error-Message");
        	if(null != responseHeaders && responseHeaders.length > 0) {
        		String errorMsg = responseHeaders[0].getValue();
        		if(StringUtils.isNotBlank(errorMsg) && !"OK".equals(errorMsg)) {
        			throw new HttpClientException(503, errorMsg);
        		}
        	}
            if (response.getStatusLine().getStatusCode() != 200) {
                throw new HttpClientException(response.getStatusLine().getStatusCode(), EntityUtils.toString(response.getEntity(), charset));
            }
            return EntityUtils.toString(response.getEntity(), charset);
        }
    }

    /**
     * @return 返回ssl httpClient(没有连接池)
     * @throws KeyStoreException
     * @throws FileNotFoundException
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws CertificateException
     * @throws UnrecoverableKeyException
     * @throws SSLInitializationException
     * @throws KeyManagementException
     */
    private CloseableHttpClient getSSLHttpClient()
            throws KeyStoreException, FileNotFoundException, IOException, NoSuchAlgorithmException,
            CertificateException, UnrecoverableKeyException, SSLInitializationException, KeyManagementException {
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        try (FileInputStream inStream = new FileInputStream(new File(this.certPathname))) {
            keyStore.load(inStream, this.certPassword.toCharArray());
        }
        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, this.certPassword.toCharArray()).build();
        // 指定TLS版本
        SSLConnectionSocketFactory sslSf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        // 设置httpclient的SSLSocketFactory
        return HttpClients.custom().setSSLSocketFactory(sslSf).build();
    }

    /**
     * 请求重试HANDLER
     *
     * @return HttpRequestRetryHandler
     */
    private HttpRequestRetryHandler getHttpRequestRetryHandler() {
        return (IOException exception, int executionCount, HttpContext context) -> {
            if (executionCount >= connectRetryNumber /*** 超过重试次数 ***/) {
                // Do not retry if over max retry count
                return false;
            }
            
            if(exception instanceof SocketTimeoutException /*** socket传输超时时出现的错误 ***/){
            	//Socket Timeout often already processed
            	return false;
            }
            
            if (exception instanceof InterruptedIOException /*** IO中断时抛出的异常 ***/) {
                // Timeout
                return false;
            }
            if (exception instanceof UnknownHostException /*** 域名解析失败 ***/) {
                // Unknown host
                return false;
            }
            if (exception instanceof SSLException /*** SSL处理失败 ***/) {
                // SSL handshake exception
                return false;
            }
            
//            HttpClientContext clientContext = HttpClientContext.adapt(context);
//            org.apache.http.HttpRequest request = clientContext.getRequest();
//            return !(request instanceof HttpEntityEnclosingRequest);
            return false;
        };
    }

    /**
     * 连接池参数设置
     *
     * @return PoolingHttpClientConnectionManager
     */
    private PoolingHttpClientConnectionManager getPoolingHttpClientConnectionManager() {
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        // Increase max total connection to 200
        cm.setMaxTotal(connectMaxTotal);
        // Increase default max connection per route to 20
        cm.setDefaultMaxPerRoute(connectMaxDefault);
        return cm;
    }


    /**
     * 连接存活策略
     *
     * @return ConnectionKeepAliveStrategy
     * @throws NumberFormatException
     */
    private ConnectionKeepAliveStrategy getConnectionKeepAliveStrategy() throws NumberFormatException {
        return (HttpResponse response, HttpContext context) -> {
            // Honor 'keep-alive' header
            HeaderElementIterator it = new BasicHeaderElementIterator(
                    response.headerIterator(HTTP.CONN_KEEP_ALIVE));
            while (it.hasNext()) {
                HeaderElement he = it.nextElement();
                String param = he.getName();
                String value = he.getValue();
                if (value != null && param.equalsIgnoreCase("timeout")) {
                    return Long.parseLong(value) * 1000;
                }
            }
            return connectKeepAliveDuration * 1000;
        };
    }

    /**
     * 连接回收策略
     *
     * @author fangqq
     */
    public class IdleConnectionMonitorThread extends Thread {
        private final HttpClientConnectionManager connMgr;
        private volatile boolean shutdown;
        private int connectRecyclingTime;

        private int connectFreeTimeout;

        public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr,
                                           int connectRecyclingTime,
                                           int connectFreeTimeout) {
            super();
            this.connectRecyclingTime = connectRecyclingTime * 1000;
            this.connectFreeTimeout = connectFreeTimeout;
            shutdown = false;
            this.connMgr = connMgr;
        }

        @Override
        public void run() {
            try {
                while (!shutdown) {
                    synchronized (this) {
                        wait(connectRecyclingTime);
                        // Close expired connections
                        connMgr.closeExpiredConnections();
                        // Optionally, close connections
                        // that have been idle longer than 30 sec
                        connMgr.closeIdleConnections(connectFreeTimeout, TimeUnit.SECONDS);
                    }
                }
            } catch (InterruptedException ex) {
                // terminate
            	logger.error("idle connections monitor thread happened a exception.{}",ex);
            }
        }

        public void shutdown() {
            shutdown = true;
            synchronized (this) {
                notifyAll();
            }
        }

    }
    
    /**
     * 下载文件
     *
     * @param uri      uri地址
     * @param filePath 文件路径
     * @return http返回流
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    public boolean downloadFile(String uri, String filePath) throws HttpClientException, SocketTimeoutException {
        return downloadFile(uri, filePath, null);
    }

    /**
     * 下载文件
     *
     * @param uri                   uri地址
     * @param filePath              文件路径
     * @param headerMap             http请求头
     * @return http返回流
     * @throws SocketTimeoutException 
     * @throws HttpClientException
     */
    public boolean downloadFile(String uri, String filePath, Map<String, String> headerMap) throws HttpClientException, SocketTimeoutException {
        if (!createFolder(filePath))
            return false;
        HttpGet httpGet = new HttpGet(uri);
        Header[] headers = getHeaders(headerMap);
        if (headers.length != 0)
            httpGet.setHeaders(headers);
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(fileTransferConnectTimeout)
                .setConnectionRequestTimeout(fileTransferConnectRequestTimeout)
                .setSocketTimeout(fileTransferSocketTimeout).build();
        httpGet.setConfig(requestConfig);
        try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
        	Header lenHeader = response.getFirstHeader("Content-Length");
        	if(lenHeader != null && "0".equals(lenHeader.getValue())){
        		return false;
        	}
            InputStream stream = response.getEntity().getContent();
            FileUtils.copyInputStreamToFile(stream, new File(filePath));
            return true;
        } catch (SocketTimeoutException e) {// 服务器响应超时：判断情况后提交/取消
        	throw e;
		} catch (IOException e) {
            throw new HttpClientException(IO_ERROR_HTTP_STATUSCODE, e);
        }
    }

    /**
     * 创建文件夹
     *
     * @param filePath 文件路径
     * @return 是否成功
     */
    private boolean createFolder(String filePath) {
        File file = new File(filePath);
        File fileFolder = new File(file.getParentFile().getAbsolutePath());
        if (!fileFolder.exists()) {
            return fileFolder.mkdirs();
        }
        return true;
    }

    /**
     * 返回标准的uri
     *
     * @param strUrl
     * @return
     * @throws HttpClientException 
     */
    private String getStandardUri(String strUrl) throws HttpClientException {
        try {
            URL url = new URL(strUrl);
            return new URI(url.getProtocol(), null, url.getHost(), url.getPort(), url.getPath(), url.getQuery(), null).toString();
		} catch (URISyntaxException | MalformedURLException e) {
			logger.error("created http client happened exception.{}",e);
			throw new HttpClientException(AUTHENTICATION_OR_CREATE_HTTPS_EXCEPTION, e);
		}
    }

    /**
     * 返回http请求头列表
     *
     * @param headerMap
     * @return
     */
    private Header[] getHeaders(Map<String, String> headerMap) {
        if (MapUtils.isEmpty(headerMap))
            return new Header[0];
        Header[] headers = new Header[headerMap.size()];
        int i = 0;
        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
            headers[i++] = new BasicHeader(entry.getKey(), entry.getValue());
        }
        return headers;
    }

    public void setCertPathname(String certPathname) {
        this.certPathname = certPathname;
    }

    public void setCertPassword(String certPassword) {
        this.certPassword = certPassword;
    }

    public void setProxyUri(String proxyUri) {
        this.proxyUri = proxyUri;
    }

    public void setProxyPort(int proxyPort) {
        this.proxyPort = proxyPort;
    }

    public void setProxyScheme(String proxyScheme) {
        this.proxyScheme = proxyScheme;
    }

    public void setProxyUserName(String proxyUserName) {
        this.proxyUserName = proxyUserName;
    }

    public void setProxyPassword(String proxyPassword) {
        this.proxyPassword = proxyPassword;
    }

    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    public void setConnectRequestTimeout(int connectRequestTimeout) {
        this.connectRequestTimeout = connectRequestTimeout;
    }

    public void setSocketTimeout(int socketTimeout) {
        this.socketTimeout = socketTimeout;
    }

	public int getFileTransferConnectTimeout() {
		return fileTransferConnectTimeout;
	}

	public void setFileTransferConnectTimeout(int fileTransferConnectTimeout) {
		this.fileTransferConnectTimeout = fileTransferConnectTimeout;
	}

	public int getFileTransferConnectRequestTimeout() {
		return fileTransferConnectRequestTimeout;
	}

	public void setFileTransferConnectRequestTimeout(int fileTransferConnectRequestTimeout) {
		this.fileTransferConnectRequestTimeout = fileTransferConnectRequestTimeout;
	}

	public int getFileTransferSocketTimeout() {
		return fileTransferSocketTimeout;
	}

	public void setFileTransferSocketTimeout(int fileTransferSocketTimeout) {
		this.fileTransferSocketTimeout = fileTransferSocketTimeout;
	}
	
	/**
	 * @author xiongxiaotun <br/>
	 * @Title: destory  <br/>
	 * @Description: 销毁方法的注解方式  <br/>
	 * @version V1.0  <br/>
	 */
	@PreDestroy
	public void destory() {
		logger.debug("destory httpClient start...");
		if(null != httpClient) {
			try {
				httpClient.close();
			} catch (IOException e) {
				logger.error("destory httpClient exception.", e);
			}
		}
		logger.debug("destory httpClient end.");
	}
}
